﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Moonlite.ExtensionApi
{
    public static class MouseHookManager
    {
        private static HookProc hookProc;
        private static IntPtr mouseHookHandle;
        private static int oldX;
        private static int oldY;
        private static Timer doubleClickTimer;
        private static MouseButtons previousClickedButtons;
        public static event MouseEventHandler MouseMove;
        public static event MouseEventHandler MouseClick;
        public static event MouseEventHandler MouseDown;
        public static event MouseEventHandler MouseUp;
        public static event MouseEventHandler MouseWheel;
        private static event MouseEventHandler mouseDoubleClick;
        public static event MouseEventHandler MouseDoubleClick
        {
            add
            {
                if (mouseDoubleClick == null)
                {
                    doubleClickTimer = new Timer
                    {
                        Interval = Win32HookFuncs.GetDoubleClickTime(),
                        Enabled = false
                    };
                    doubleClickTimer.Tick += OnDoubleClickTimerTick;
                    MouseUp += OnMouseUp;
                }
                mouseDoubleClick += value;
            }
            remove
            {
                if (mouseDoubleClick != null)
                {
                    mouseDoubleClick -= value;
                    if (mouseDoubleClick == null)
                    {
                        MouseUp -= OnMouseUp;
                        doubleClickTimer.Tick -= OnDoubleClickTimerTick;
                        doubleClickTimer = null;
                    }
                }
            }
        }
        private static void OnDoubleClickTimerTick(object sender, EventArgs e)
        {
            doubleClickTimer.Enabled = false;
            previousClickedButtons = MouseButtons.None;
        }
        private static void OnMouseUp(object sender, MouseEventArgs e)
        {
            if (e.Clicks < 1) return;
            if (e.Button.Equals(previousClickedButtons))
            {
                if (mouseDoubleClick != null)
                {
                    mouseDoubleClick.Invoke(null, e);
                }
                doubleClickTimer.Enabled = false;
                previousClickedButtons = MouseButtons.None;
            }
            else
            {
                doubleClickTimer.Enabled = true;
                previousClickedButtons = e.Button;
            }
        }
        private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                MouseLLHookStruct mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
                MouseButtons button = MouseButtons.None;
                short mouseDelta = 0;
                int clickCount = 0;
                bool mouseDown = false;
                bool mouseUp = false;
                switch (wParam)
                {
                    case Win32HookFuncs.WM_LBUTTONDOWN:
                        mouseDown = true;
                        button = MouseButtons.Left;
                        clickCount = 1;
                        break;
                    case Win32HookFuncs.WM_LBUTTONUP:
                        mouseUp = true;
                        button = MouseButtons.Left;
                        clickCount = 1;
                        break;
                    case Win32HookFuncs.WM_LBUTTONDBLCLK:
                        button = MouseButtons.Left;
                        clickCount = 2;
                        break;
                    case Win32HookFuncs.WM_RBUTTONDOWN:
                        mouseDown = true;
                        button = MouseButtons.Right;
                        clickCount = 1;
                        break;
                    case Win32HookFuncs.WM_RBUTTONUP:
                        mouseUp = true;
                        button = MouseButtons.Right;
                        clickCount = 1;
                        break;
                    case Win32HookFuncs.WM_RBUTTONDBLCLK:
                        button = MouseButtons.Right;
                        clickCount = 2;
                        break;
                    case Win32HookFuncs.WM_MOUSEWHEEL:
                        mouseDelta = (short)((mouseHookStruct.MouseData >> 16) & 0xffff);
                        break;
                }
                MouseEventArgs e = new MouseEventArgs(button, clickCount, mouseHookStruct.Point.X, mouseHookStruct.Point.Y, mouseDelta);
                if (MouseUp != null && mouseUp)
                {
                    MouseUp.Invoke(null, e);
                }
                if (MouseDown != null && mouseDown)
                {
                    MouseDown.Invoke(null, e);
                }
                if (MouseClick != null && clickCount > 0)
                {
                    MouseClick.Invoke(null, e);
                }
                if (mouseDoubleClick != null && clickCount == 2)
                {
                    mouseDoubleClick.Invoke(null, e);
                }
                if (MouseWheel != null && mouseDelta != 0)
                {
                    MouseWheel.Invoke(null, e);
                }
                if (MouseMove != null && (oldX != mouseHookStruct.Point.X || oldY != mouseHookStruct.Point.Y))
                {
                    oldX = mouseHookStruct.Point.X;
                    oldY = mouseHookStruct.Point.Y;
                    if (MouseMove != null)
                    {
                        MouseMove.Invoke(null, e);
                    }
                }
            }
            return Win32HookFuncs.CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
        }
        public static void Install()
        {
            if (mouseHookHandle == IntPtr.Zero)
            {
                hookProc = MouseHookProc;
                mouseHookHandle = Win32HookFuncs.SetWindowsHookEx(Win32HookFuncs.WH_MOUSE_LL, hookProc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
                if (mouseHookHandle == IntPtr.Zero)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    throw new Win32Exception(errorCode);
                }
            }
        }
        public static void Uninstall()
        {
            if (mouseHookHandle != IntPtr.Zero)
            {
                int result = Win32HookFuncs.UnhookWindowsHookEx(mouseHookHandle);
                mouseHookHandle = IntPtr.Zero;
                hookProc = null;
                if (result == 0)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    throw new Win32Exception(errorCode);
                }
            }
        }
    }
}
